#include "dicom/handler/file_upload_handler.h"

#include <onion/shortcuts.h>
#include <gwbase/base/util.h>
#include <gwbase/base/logging.h>
#include <gwbase/base/thread.h>
#include <json/json.h>
#include "dicom/web_server.h"
#include "dicom/session.h"
#include "dicom/dmv_db.h"
#include "dicom/file_object.h"
#include "dicom/handler/handler_common.h"
#include "dicom/sync_manager.h"
#include <surbasic/Digestor.h>

namespace dicom {

static HttpHandler* CreateFileUploadHandler(onion_request* conn) {
  return new FileUploadHandler(conn);
}

void FileUploadHandler::RegisterHandler(WebServer* s) {
  s->AddHandler("/api/file/upload", "PUT",
                boost::bind(CreateFileUploadHandler, _1));
}

static std::string GetBestHexkeyFromCache(HashCache* c,
                                          const std::string& md5) {
  HashEntry entry;
  if (c->GetBestEntry(md5, &entry) == true && !entry.hexkey.empty()) {
    return entry.hexkey;
  }
  return dicom::ByteArray::genRandom(32).toHexStr();
}

Status FileUploadHandler::SavePlainFileToCache(const std::string fid,
                                               const std::string& filename,
                                               HashEntry* hash) {
  LRUFileCache* cache = GetGateway()->cache();
  HashCache* hcache = GetGateway()->hcache();

  std::string src_md5 = Digestor::MD5File(filename, NULL);
  if (src_md5.empty()) {
    return Status(Status::kOther, "can't get md5 of uploaded temp file");
  }

  std::string hex_key = GetBestHexkeyFromCache(hcache, src_md5);
  LRUFileCache::Handle* h =
      SaveFileToCacheWithAES(cache, filename, fid, hex_key, hash);
  if (h == NULL) {
    return Status(Status::kOther, "Failed to save file to cache");
  }
  cache->Release(h);
  hash->status = HASH_LOCAL;
  assert(hash->IsComplete());
  return Status();
}

Status FileUploadHandler::SaveEncryptFileToCache(const std::string fid,
                                                 const std::string& filename,
                                                 HashEntry* hash) {
  LRUFileCache* cache = GetGateway()->cache();
  PutSinkPool* pool = GetGateway()->put_sink_pool();
  PutSink* sink = pool->FindSink(filename);
  if (sink == NULL) {
    return Status(Status::kOther, "Can't find PUT sink");
  }

  sink->enc->GetHash(hash);
  hash->status = HASH_LOCAL;
  assert(hash->IsComplete());

  LRUFileCache::Handle* h;
  h = cache->InsertFromTmpFile(fid, sink->name, sink->src_size,
                               hash->src_digest, hash->enc_digest,
                               Sticky::sticky);
  if (h == NULL) {
    return Status(Status::kOther, "Can't move encrypted file to cache");
  }
  cache->Release(h);
  return Status();
}

int FileUploadHandler::HandleRequest(onion_request* conn,
                                     onion_response* resp) {
  // file upload
  Status s;
  CredentialPtr c;
  s = CheckToken(conn, &c);
  if (!s.ok()) {
    SendSimpleResponse(resp, 401, "authorization error");
    return OCS_PROCESSED;
  }

  const char* filename = onion_request_get_file(conn, "filename");
  if (filename == NULL) {
    SendSimpleResponse(resp, 400, "no data uploaded");
    return OCS_PROCESSED;
  }

  const std::string pid = GetQueryString(conn, "pid");
  const std::string name = GetQueryString(conn, "name");
  if (pid.empty() || name.empty()) {
    SendSimpleResponse(resp, 400, "pid or name is empty");
    return OCS_PROCESSED;
  }

  FileInfo fi;
  s = StatPath(filename, &fi);
  if (!s.ok() || !fi.is_regular()) {
    SendSimpleResponse(resp, 500, "can't stat the uploaded temp file");
    return OCS_PROCESSED;
  }

  LRUFileCache* cache = GetGateway()->cache();
  HashCache* hcache = GetGateway()->hcache();
  DmvDBPtr db = GetGateway()->dmv_mgr()->GetDB(c->email);
  DirObject pdir;
  if (db->GetDirByID(pid, &pdir) == false) {
    SendSimpleResponse(resp, 404, "invalid parent directory id");
    return OCS_PROCESSED;
  }

  LOG_INFO << "REQUEST UPLOAD: " << pid << ": " << name << ": " << fi.size;

  LogHippaOperation(OP_UPLOAD_FILE, db, c, pid, name);

  EntryLockGuard entry_lock(db, pid, name);
  FileObject old;

  // create a new file id
  std::string fid = GenFileId();
  /*
     if (db->GetSubFileByName(pid, name, &old) == true) {
     fid = old.local_id;
     } */
  HashEntry hash;
  s = SaveEncryptFileToCache(fid, filename, &hash);
  if (!s.ok()) {
    LOG_ERROR << s;
    SendSimpleResponse(resp, 500, "internal local cache error");
    return OCS_PROCESSED;
  }

  if (db->GetSubFileByName(pid, name, &old) == true) {
    db->DeleteFile(old.local_id);
    cache->Erase(old.local_id);
    GetGateway()->sync_mgr()->DeleteTask(old.local_id);
  }

  FileObject fo;
  fo.local_id = fid;
  fo.parentId = pid;
  fo.name = name;
  fo.createTime = Timestamp::Now();
  fo.modifiedTime =
      Timestamp::FromTimeT(dicom::GetQueryStringInt64(conn, "mtime", 0));
  fo.creator = c->email;
  fo.size = hash.src_size;
  fo.digest = hash.src_md5;
  fo.hex_key = hash.hexkey;
  std::string r = fo.ToJson();
  db->UpdateFile(fo);

  Json::Value v;
  v["code"] = 0;
  v["message"] = "success";
  Json::Value item;
  fo.ToJson(item);
  v["item"] = item;

  // add to sync task queue
  GetGateway()->sync_mgr()->AddNewTask(c->email, fid, pid);

  db->UpdateParentDirModifyTime(pid);
  hcache->UpdateHashEntry(&hash);

  SendJsonResult(resp, 200, v);
  return OCS_PROCESSED;
}
}
